import { app } from "../app";

/**
 * 所有manager的父类
 * 
 * 生命周期：[onLoad] -> [onEnable] -> [init] -> [start]
 * 生命周期：只有onFinished和onFinished执行之后的update是保证有序的
 * 
 * init被调用时，代表正处于manager初始化过程中，此时app.manager下的[用户自定义manager]不能确定哪些是可用
 * 子类中可以自定义init、load或preload方法，可参考uiManager和soundManager
 */
const { ccclass } = cc._decorator;

const dotReWriteFuns = ['emit', 'on', 'once', 'off', 'targetOff', '__initMgr'];

const UserManagerRoot = 'Canvas/UserManager';

@ccclass
export default abstract class baseManager extends cc.Component {
    // 同node.zIndex
    private sort: number = 0;
    // 事件管理器
    private event: cc.EventTarget = new cc.EventTarget();

    // asset bundle
    private bundleName: string = '';

    // manager名字
    private _managerName: string = cc.js.getClassName(this);
    public get managerName() {
        return this._managerName;
    }

    // 事件
    public static EventType = cc.Enum({})

    // sort影响manager的onFinished顺序, 通过设置node.zIndex实现, node.zIndex会在onLoad、onEnable之后、init之前被赋值
    constructor(sort?: number);
    constructor(...args) {
        super();

        this.sort = typeof args[0] === 'number' ? args[0] : 0;

        if (CC_EDITOR) {
            dotReWriteFuns.forEach((funName) => {
                if (baseManager.prototype[funName] !== this[funName]) {
                    Editor.warn(`[${this._managerName}] [warn] 不应该重写父类方法{${funName}}`);
                }
            });
        }

        if (app && app.appReady) {
            if (this._managerName !== 'Manager' && this._managerName.slice(-7) === 'Manager') {
                const managerName = this._managerName.slice(0, - 7);
                app.Manager[managerName] = this.constructor;
                app.manager[managerName] = this;
            } else if (CC_EDITOR) {
                Editor.error(`[${this._managerName}] [error] manager命名错误(应为 xxxxManager 以Manager结尾)`);
            } else if (CC_DEBUG) {
                cc.error(`[${this._managerName}] [error] manager命名错误(应为 xxxxManager 以Manager结尾)`);
            }
        }
    }

    // 用来初始化组件或节点的一些属性，当该组件被第一次添加到节点上或用户点击了它的 Reset 菜单时调用。这个回调只会在编辑器下调用。
    protected resetInEditor() {
        const widget = this.node.getComponent(cc.Widget) || this.node.addComponent(cc.Widget);
        widget.isAlignBottom = true;
        widget.isAlignLeft = true;
        widget.isAlignRight = true;
        widget.isAlignTop = true;
        widget.top = 0;
        widget.left = 0;
        widget.right = 0;
        widget.bottom = 0;
        widget.alignMode = cc.Widget.AlignMode.ON_WINDOW_RESIZE;
    }

    /**
     * 禁止重写
     * @param finish 
     */
    private __initMgr(finish) {
        this.node.zIndex = this.sort;
        return this.init(finish)
    }

    /**
     * [无序] 自身初始化完成, init执行完毕后被调用
     */
    protected onInited() {
    }

    /**
     * [有序] 所有manager初始化完成
     */
    protected onFinished() {
    }

    /**
     * [无序] 初始化manager，在初始化完成后，调用finish方法
     * @param {Function} finish 
     */
    protected init(finish?: Function, bundleName?: string) {
        this.bundleName = bundleName || '';

        const setting = app.setting[this._managerName];

        if (!setting) {
            return finish && finish();
        }

        if (setting.property) {
            const property = setting.property;
            for (const key in property) {
                if (Object.prototype.hasOwnProperty.call(property, key)) {
                    this[key] = property[key];
                }
            }
        }

        if (!setting.static || !setting.static.length) {
            return finish && finish();
        }

        if (!bundleName) {
            return this.error('配置static，第二个参数必须指定bundleName');
        }

        baseManager.getBundle(this.bundleName, function (bundle) {
            const async = app.lib.task.createASync();
            setting.static.forEach((path) => {
                async.add((next, retry) => {
                    cc.assetManager.preloadAny({ path, bundle: bundleName }, function (error, result) {
                        if (error || !result) {
                            setTimeout(retry, 500);
                        } else {
                            next();
                        }
                    });
                })
            })
            async.start(finish as any);
        })
    }

    /**
     * 预加载
     */
    public preload(paths: string | string[], type: typeof cc.Asset, onProgress: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preload(paths: string | string[], onProgress: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preload(paths: string | string[], type: typeof cc.Asset, onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preload(paths: string | string[], type: typeof cc.Asset): void;
    public preload(paths: string | string[], onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preload(paths: string | string[]): void;
    public preload(...args: any[]) {
        if (!this.bundleName) return this.error(`请先初始化`);

        baseManager.getBundle(this.bundleName, function (bundle) {
            bundle.preload(args[0], args[1], args[2], args[3]);
        })
    }

    /**
     * 预加载
     */
    public preloadDir(dir: string, type: typeof cc.Asset, onProgress: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preloadDir(dir: string, onProgress: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void, onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preloadDir(dir: string, type: typeof cc.Asset, onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preloadDir(dir: string, type: typeof cc.Asset): void;
    public preloadDir(dir: string, onComplete: (error: Error, items: cc.AssetManager.RequestItem[]) => void): void;
    public preloadDir(dir: string): void;
    public preloadDir(...args: any[]) {
        if (!this.bundleName) return this.error(`请先初始化`);

        baseManager.getBundle(this.bundleName, function (bundle) {
            bundle.preloadDir(args[0], args[1], args[2], args[3]);
        })
    }

    /**
     * 加载
     * @param {*} res 
     * @param {(num:Number,total:Number)=>{}} progress 
     * @param {(success:Boolean)=>{}} complete
     * @example
     * load(resUrl, complete)
     * load(resUrl, progress, complete)
     */
    public load(res: { path: string, type: Function } | string, progress?: Function, complete?: Function) {
        if (!this.bundleName) return this.error(`请先初始化`);

        if (!complete) {
            complete = progress;
            progress = undefined;
        }

        if (!res) {
            this.error(`load fail. url is ${JSON.stringify(res)}`);
            return complete && complete(null);
        }

        const args = [];

        if (typeof res === 'string') {
            args.push(res);
        } else {
            args.push(res.path);
            args.push(res.type);
        }

        if (progress) {
            args.push(progress);
        }

        args.push((err, res) => {
            if (err) {
                this.error(`load "${args[0]}" fail`);
                complete && complete(null);
            } else {
                complete && complete(res);
            }
        });

        baseManager.getBundle(this.bundleName, function (bundle) {
            bundle.load(args[0], args[1], args[2], args[3]);
        })
    }

    /**
     * 销毁操作
     * @param {*} res
     */
    public release(res: string, type?: typeof cc.Asset) {
        const bundle = cc.assetManager.getBundle(this.bundleName);
        bundle && bundle.release(res, type);
    }

    protected log(str, ...args) {
        console.log(`[${this._managerName}] [log] ${str}`, ...args);
    }
    protected warn(str, ...args) {
        console.warn(`[${this._managerName}] [warn] ${str}`, ...args);
    }
    protected error(str, ...args) {
        console.error(`[${this._managerName}] [error] ${str}`, ...args);
    }

    public emit(event: string, ...data) {
        this.event.emit(event, ...data);
    }

    public on(event: string, cb: Function, target?: any) {
        this.event.on(event, cb, target);
    }

    public once(event: string, cb: () => void, target?: any) {
        this.event.once(event, cb, target);
    }

    public off(event: string, cb: Function, target?: any) {
        this.event.off(event, cb, target);
    }

    public targetOff(target) {
        this.event.targetOff(target);
    }

    /***********************************静态***********************************/
    /**
     * manager asset bundle
     */
    private static bundleName: string = 'app-manager';
    private static bundle: cc.AssetManager.Bundle = null;

    /**
     * 系统内置manager的数量
     */
    public static get sysMgrCount() {
        return 4;
    };

    /**
     * 初始化操作
     */
    public static init(onFinish: Function) {
        this.getBundle(this.bundleName, (bundle) => {
            this.bundle = bundle;
            onFinish && onFinish();
        })
    }

    /**
     * 初始化Bundle [一定成功]
     * @param name 
     * @param cb 
     */
    public static getBundle(name: string, cb: (bundle: cc.AssetManager.Bundle) => void) {
        app.lib.task.excute((retry) => {
            cc.assetManager.loadBundle(name, (err, bundle) => {
                if (bundle && !err) {
                    cb(bundle);
                } else {
                    setTimeout(retry, 500);
                }
            })
        })
    }

    /**
     * 获得初始化资源的数量
     */
    public static getInitAssetNum() {
        if (!this.bundle) throw Error('请先初始化');

        const array = this.bundle.getDirWithPath('/', cc.Prefab) as { uuid: string, path: string, ctor: Function }[];

        let count = 0;

        array.forEach(function (item) {
            if (item.path.endsWith('Manager')) {
                count++;
            }
        })

        return count + this.sysMgrCount;
    }

    /**
     * 获得初始化资源的数量
     */
    public static getInitAssetUrls() {
        if (!this.bundle) throw Error('请先初始化');

        const array = this.bundle.getDirWithPath('/', cc.Prefab) as { uuid: string, path: string, ctor: Function }[];

        const pathArr: string[] = [];

        array.forEach(function (item) {
            if (item.path.endsWith('Manager')) {
                pathArr.push(item.path);
            }
        })

        return pathArr;
    }

    /**
     * 静态方法，初始化manager，该方法必须在场景初始化完毕之后调用
     * @param {(completeAsset:Number, totalAsset:Number)=>{}} progress 
     * @param {(totalAsset:Number)=>{}} complete 
     */
    public static initManagers(progress, complete) {
        if (!this.bundle) throw Error('请先初始化');

        const bundle = this.bundle;
        const urls = this.getInitAssetUrls();

        const totalAsset = urls.length + this.sysMgrCount;
        let completeAsset = 0;

        const onProgress = function (next, manager) {
            if (CC_DEBUG) cc.log(`[baseManager] [log] [beganProgress] %c${manager.managerName}`, 'color:red');
            return function () {
                if (CC_DEBUG) cc.log(`[baseManager] [log] [endedProgress] %c${manager.managerName}`, 'color:green');
                manager.onInited();
                progress && progress(++completeAsset, totalAsset);
                next();
            }
        }

        // 初始化系统manager
        const aSync1 = app.lib.task.createASync();
        const sysMgr = [app.manager.event, app.manager.timer, app.manager.sound, app.manager.ui];
        sysMgr.forEach(function (manager) {
            aSync1.add(function (next) {
                baseManager.prototype.__initMgr.call(manager, onProgress(next, manager));
            });
        })

        // 初始化用户manager
        const aSync2 = app.lib.task.createASync();
        const userManagerRoot = cc.find(UserManagerRoot);
        urls.forEach(function (url) {
            aSync2.add(function (next, retry) {
                bundle.load(url, cc.Prefab, function (err, prefab: cc.Prefab) {
                    if (err) {
                        cc.log(`[baseManager] [log] [initManager] load ${url} fail, retry...`);
                        setTimeout(retry, 500);
                    } else {
                        const node: cc.Node = cc.instantiate(prefab);
                        node.active = true;
                        node.parent = userManagerRoot;
                        const manager = node.getComponent(baseManager);
                        manager.__initMgr(onProgress(next, manager));
                    }
                })
            })
        })

        const sync = app.lib.task.createSync();

        sync.add(function (next) {
            aSync1.start(next)
        });
        sync.add(function (next) {
            aSync2.start(next)
        });

        sync.start(function () {
            sysMgr.forEach(function (manager: baseManager) {
                manager.onFinished();
            })

            userManagerRoot.sortAllChildren();
            userManagerRoot.children.forEach(function (node) {
                const manager = node.getComponent(baseManager);
                manager.onFinished();
            })

            complete && complete(totalAsset);
        })
    }
}